﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TekaMUD.Engine.Blocks;
using TekaMUD.Engine.Behaviour;
using TekaMUD.Engine.Framework;

namespace TekaMUD.Engine
{
	public class EntityManager
	{
		private static object SyncRoot = new object();
		private static Dictionary<Type, List<GameEntity>> database;
		private static Dictionary<Guid, GameEntity> id_database;
		private static Type ROOT_TYPE = typeof(GameEntity);

		static EntityManager()
		{
			database = new Dictionary<Type, List<GameEntity>>();
			id_database = new Dictionary<Guid, GameEntity>();
		}

		public static void RegisterInstance(GameEntity o)
		{
			lock (SyncRoot)
			{
				if(id_database.ContainsKey(o.UID))
					throw new UserException("Object " + o.UID + " already registered.");
				else
					id_database[o.UID] = o;

				Type t = o.GetType();

				// Add the type's parent classes (up to ROOT_TYPE) to our lists.
				for (Type ptr = t; ptr != null && ptr != ROOT_TYPE; ptr = ptr.BaseType)
				{
					List<GameEntity> list;

					if(! database.TryGetValue(ptr, out list))
						database[ptr] = new List<GameEntity>(new GameEntity[] { o });
					else
						list.Add(o);
				}
			}
		}

		public static GameEntity[] GetByType(Type t)
		{
			List<GameEntity> ret;

			lock (SyncRoot)
				if (database.TryGetValue(t, out ret))
					return ret.ToArray();
				else
					return new GameEntity[] { };
		}

		public static void DestroyInstance(GameEntity o)
		{
			lock (SyncRoot)
			{
				if (!id_database.ContainsKey(o.UID))
					return;
				else
					id_database.Remove(o.UID);

				Type t = o.GetType();

				for (Type ptr = t; ptr != null && ptr != ROOT_TYPE; ptr = ptr.BaseType)
				{
					List<GameEntity> list = database[t];

					if (list == null)
						continue;
					else
						list.Remove(o);
				}
			}
		}

		public static Dictionary<Type, List<object>> CloneContents()
		{
			Dictionary<Type, List<object>> db = new Dictionary<Type, List<object>>();

			lock (SyncRoot)
			{
				foreach (GameEntity ge in id_database.Values)
				{
					List<object> list;
					Type t = ge.GetType();

					if (db.TryGetValue(t, out list))
						list.Add(ge);
					else
						db[t] = new List<object>(new object[] { ge });
				}
			  
			}

			return db;
		}

		public static AbstractSpawnPoint[] GetSpawnPoints()
		{
			lock (SyncRoot)
				return (AbstractSpawnPoint[])GetByType(typeof(AbstractSpawnPoint));
		}

		public static NPC[] GetAllNPCs()
		{
			lock (SyncRoot)
				return Array.ConvertAll<GameEntity, NPC>(
					GetByType(typeof(NPC)),
					delegate(GameEntity ge)
					{
						return ge as NPC;
					});
		}

		public static RationalAgent[] GetAllRationalAgents()
		{
			lock (SyncRoot)
				return Array.ConvertAll<GameEntity, RationalAgent>(
					GetByType(typeof(RationalAgent)),
					delegate(GameEntity ge)
					{
						return ge as RationalAgent;
					});
		}

		public static Player[] GetAllPlayers()
		{
			lock (SyncRoot)
				return Array.ConvertAll<GameEntity, Player>(
					GetByType(typeof(Player)),
					delegate(GameEntity ge)
					{
						return ge as Player;
					});
		}

		public static NPC GetNPCByID(Guid guid)
		{
			GameEntity ret;

			lock (SyncRoot)
				if (id_database.TryGetValue(guid, out ret))
					return ret as NPC;
				else
					return null;		
		}

		public static Room GetRoomByID(Guid guid)
		{
			GameEntity ret;

			lock (SyncRoot)
				if (id_database.TryGetValue(guid, out ret))
					return ret as Room;
				else
					return null;
		}


		public static void Unregister(GameEntity o)
		{
			lock (SyncRoot)
			{
				if (!id_database.ContainsKey(o.UID))
					throw new UserException("Object " + o.UID + " not registered.");

				id_database.Remove(o.UID);
				Type t = o.GetType();

				// Add the type's parent classes (up to ROOT_TYPE) to our lists.
				for (Type ptr = t; ptr != null && ptr != ROOT_TYPE; ptr = ptr.BaseType)
				{
					List<GameEntity> list;

					if (database.TryGetValue(ptr, out list))
						list.Remove(o);
				}
			}
		}
	}
}
